using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace ReactiveLang
{
    [ContractClass(typeof(IStatementParentContract<>))]
    public interface IStatementParent<in T> : IExpressionParent<Unit>
    {
        IDisposable OnReturn(IExpression<T> value);
        IDisposable OnBreak();
        IDisposable OnContinue();
    }

    #region Contract for IStatementParent<T>

    [ContractClassFor(typeof(IStatementParent<>))]
    abstract class IStatementParentContract<T> : IStatementParent<T>
    {
        public IDisposable OnReturn(IExpression<T> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            throw new NotImplementedException();
        }

        public IDisposable OnBreak()
        {
            throw new NotImplementedException();
        }

        public IDisposable OnContinue()
        {
            throw new NotImplementedException();
        }

        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        public void OnException(Exception exception)
        {
        }

        public void OnValue(Unit value)
        {
        }
    }

    #endregion

    public static class StatementParent
    {
        sealed class AnonymousStatementParent<T> : IStatementParent<T>
        {
            internal Func<IExpression<T>, IDisposable> _onReturn;
            public IDisposable OnReturn(IExpression<T> value)
            {
                return _onReturn(value);
            }

            internal Func<IDisposable> _onBreak;
            public IDisposable OnBreak()
            {
                return _onBreak();
            }

            internal Func<IDisposable> _onContinue;
            public IDisposable OnContinue()
            {
                return _onContinue();
            }

            internal Action<Exception> _onException;
            public void OnException(Exception exception)
            {
                _onException(exception);
            }

            public void OnValue(Unit value)
            {
                
            }
        }
        public static IStatementParent<T> Create<T>(Action<Exception> onException, Func<IExpression<T>, IDisposable> onReturn,
            Func<IDisposable> onBreak, Func<IDisposable> onContinue)
        {
            Contract.Requires<ArgumentNullException>(onException != null, "onException");
            Contract.Requires<ArgumentNullException>(onReturn != null, "onReturn");
            Contract.Requires<ArgumentNullException>(onBreak != null, "onBreak");
            Contract.Requires<ArgumentNullException>(onContinue != null, "onContinue");

            return new AnonymousStatementParent<T>
            {
                _onException = onException,
                _onReturn = onReturn,
                _onBreak = onBreak,
                _onContinue = onContinue
            };
        }

        internal static IStatementParent<T> Derive<T>(this IStatementParent<T> grandParent,
            Action<Exception> onException = null, Func<IExpression<T>, IDisposable> onReturn = null,
            Func<IDisposable> onBreak = null, Func<IDisposable> onContinue = null)
        {
            Contract.Requires<ArgumentNullException>(grandParent != null, "grandParent");
            return Create(
                onException ?? grandParent.OnException,
                onReturn ?? grandParent.OnReturn,
                onBreak ?? grandParent.OnBreak,
                onContinue ?? grandParent.OnContinue
            );
        }
    }
}